Animations in mayavi


In [1]:
import sympy as sp
import numpy as np
%matplotlib nbagg
from matplotlib import pyplot as plt

class hamiltonian_system:
    def __init__(self, x, p, H):
        assert(len(x) == len(p))
        self.H = H
        self.x = x
        self.p = p
        self.degrees_of_freedom = len(self.x)
        self.rhs = ([sp.utilities.lambdify(tuple(self.x)+tuple(self.p),
                                           self.H.diff(self.p[i]),
                                           modules = 'numpy')
                     for i in range(self.degrees_of_freedom)] +
                    [sp.utilities.lambdify(tuple(self.x)+tuple(self.p),
                                           -self.H.diff(self.x[i]),
                                           modules = 'numpy')
                     for i in range(self.degrees_of_freedom)])
        self.energy = sp.utilities.lambdify(tuple(self.x)+tuple(self.p),
                                            self.H,
                                            modules = 'numpy')
        return None
    def numpy_rhs(self, point):
        return np.array([self.rhs[i](*tuple(point)) * np.ones(point.shape[1:])
                         for i in range(2*self.degrees_of_freedom)])

In [2]:
def Euler(rhs, dt = 0.5, N = 8, x0 = None):
    x = np.zeros((N+1,) + x0.shape,
                 dtype = x0.dtype)
    x[0] = x0
    for t in range(N):
        x[t+1] = x[t] + dt*rhs(x[t])
    return x

def cRK(rhs, dt = 0.5, N = 8, x0 = None):
    x = np.zeros((N+1,) + x0.shape,
                 dtype = x0.dtype)
    x[0] = x0
    for t in range(N):
        k1 = rhs(x[t])
        k2 = rhs(x[t] + 0.5*dt*k1)
        k3 = rhs(x[t] + 0.5*dt*k2)
        k4 = rhs(x[t] + dt*k3)
        x[t+1] = x[t] + dt*(k1 + 2*(k2 + k3) + k4)/6
    return x

def get_evdt(
        initial_condition,
        h0, N0, ndivisions,
        method,
        rhs):
    epsilon = []
    tstep = [h0]
    sol = []

    N = N0
    sol.append(method(rhs,
                      dt = tstep[-1],
                      N = N,
                      x0 = initial_condition))
    for n in range(1, ndivisions + 1):
        tstep.append(h0*2.**(-n))
        N = N*2
        sol.append(method(rhs,
                          dt = tstep[-1],
                          N = N,
                          x0 = initial_condition))
        epsilon.append((sol[-1][-1] - sol[-2][-1]) /
                       np.max(np.abs(sol[-1])))

    return (np.array(tstep[:len(tstep)-1]),
            np.abs(epsilon))

In [3]:
nparticles = 8

position = []
momentum = []
for i in range(nparticles):
    position += [sp.Symbol('x{0}'.format(i)),
                 sp.Symbol('y{0}'.format(i)),
                 sp.Symbol('z{0}'.format(i))]
    momentum += [sp.Symbol('p{0}'.format(i)),
                 sp.Symbol('q{0}'.format(i)),
                 sp.Symbol('r{0}'.format(i))]

def VanDerWaals(r1, r2):
    dist = sp.sqrt((r1[0] - r2[0])**2 + (r1[1] - r2[1])**2 + (r1[2] - r2[2])**2)
    return dist**(-12) - dist**(-6)

def Box(r, exponent = 4):
    return ((r[0]/(nparticles))**(2*exponent) +
            (r[1]/(nparticles))**(2*exponent) +
            (r[2]/(nparticles))**(2*exponent))

# first, put in kinetic energy term
H = sum(p**2/2 for p in momentum)

# second, put in walls
H += sum(Box(position[3*i:3*(i+1)]) for i in range(nparticles))

# third, put in pairwise interactions
for i in range(nparticles):
    for j in range(i+1, nparticles):
        H += VanDerWaals(position[3*i:3*(i+1)],
                         position[3*j:3*(j+1)])
        
bla = hamiltonian_system(position, momentum, H)

In [4]:
initial_condition = np.zeros(nparticles*6, dtype = np.float)
initial_condition[0:3*nparticles:3] = \
    np.linspace(-nparticles/2, nparticles/2, nparticles)
alpha = np.random.random()*np.pi*2
beta = np.random.random()*np.pi
initial_condition[3*nparticles  ] = 2*(np.sin(beta)*np.cos(alpha)+2)
initial_condition[3*nparticles+1] = 2*(np.sin(beta)*np.sin(alpha))
initial_condition[3*nparticles+2] = np.cos(beta)

print initial_condition


[-4.          0.          0.         -2.85714286  0.          0.
 -1.71428571  0.          0.         -0.57142857  0.          0.
  0.57142857  0.          0.          1.71428571  0.          0.
  2.85714286  0.          0.          4.          0.          0.
  5.57527034  1.22172167 -0.08049772  0.          0.          0.          0.
  0.          0.          0.          0.          0.          0.          0.
  0.          0.          0.          0.          0.          0.          0.
  0.          0.          0.        ]

In [5]:
x = np.linspace(0.9, 3, 128)
a = plt.figure(figsize = (6, 3)).add_subplot(111)
a.plot(x, x**(-12) - x**(-6))
a.plot(x, x - x, color = (0, 0, 0), dashes = (2, 2))
a.set_title('Van der Waals potential')
# basically, particles will attract if they're further apart than 1.1,
# and repell otherwise. in practice, the attraction is negligible if the distance
# is larger than 2 (since the derivative of U is teeny).


Out[5]:
<matplotlib.text.Text at 0x7f6471ca0850>

In [6]:
dt, err = get_evdt(initial_condition,
                   2.**(-6), 16, 6,
                   cRK,
                   bla.numpy_rhs)

fig = plt.figure()
a = fig.add_subplot(111)
a.plot(dt, np.average(err, axis = 1), marker = '.')
a.set_xscale('log')
a.set_yscale('log')



In [7]:
from mayavi import mlab

sol = cRK(bla.numpy_rhs,
          dt = 2.**(-6),
          N = 2**7,
          x0 = initial_condition)


WARNING:traits.has_traits:DEPRECATED: traits.has_traits.wrapped_class, 'the 'implements' class advisor has been deprecated. Use the 'provides' class decorator.

In [8]:
points = mlab.points3d(sol[0, 0:3*nparticles:3],
                       sol[0, 1:3*nparticles:3],
                       sol[0, 2:3*nparticles:3],
                       scale_mode = 'none',
                       scale_factor = 0.9)
# words written with "@" in front are called decorators,
# and they are basically functions that are applied to the function
# defined underneath, as in https://www.python.org/dev/peps/pep-0318/#id71
# something like that.
# if you have the patience, you can read the whole thing
# https://www.python.org/dev/peps/pep-0318

# http://docs.enthought.com/mayavi/mayavi/mlab_animating.html
@mlab.show
@mlab.animate(delay=250)
def anim():
    t = 0
    while 1:
        points.mlab_source.set(x = sol[t, 0:3*nparticles:3],
                               y = sol[t, 1:3*nparticles:3],
                               z = sol[t, 2:3*nparticles:3])
        t = (t + 1) % sol.shape[0]
        yield

anim()

Very nice explanation of the yield keyword is available at http://stackoverflow.com/a/231855/4205267. Basically, anim returns a "generator", a function that will act like a list that can only be traversed once (conceptually a queue). What happens in practice is that the while loop is executed once for each frame, and that's what we care about.


In [19]:
initial_condition = np.zeros(nparticles*6, dtype = np.float)
initial_condition[3*nparticles:] = np.random.randn(nparticles*3)

initial_condition[0:3*nparticles:3] = \
    np.linspace(-nparticles/2, nparticles/2, nparticles)


sol = cRK(bla.numpy_rhs,
          dt = 2.**(-7),
          N = 2**12,
          x0 = initial_condition)

In [20]:
points = mlab.points3d(sol[0, 0:3*nparticles:3],
                       sol[0, 1:3*nparticles:3],
                       sol[0, 2:3*nparticles:3],
                       scale_mode = 'none',
                       scale_factor = 0.9)
@mlab.show
@mlab.animate(delay=50)
def anim():
    t = 0
    while 1:
        points.mlab_source.set(x = sol[t, 0:3*nparticles:3],
                               y = sol[t, 1:3*nparticles:3],
                               z = sol[t, 2:3*nparticles:3])
        t = (t + 10) % sol.shape[0]
        yield

anim()

In [ ]: